//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================

#include <engine.h>

#ifdef _DEBUG
Int32 g_windowBufferSize;
#endif

BufferCache g_bufferCache;
extern Int32 g_screenSize;

Int32 BufferCacheNode::Release()
{
    assert(m_cache);

    Int32 ref = m_reference.Decrement();

    assert(ref >= 0);

    GFX_EXPRESSION(DEBUG_BUFFERREF,
        kprintf("Buffer Node 0x%08x after Release reference = %d\n",
        this, ref));

    if (ref) {
        return ref;
    }

    g_lock.Lock();

    if (m_surface) {
        m_surface->m_cacheBuffer = NULL;
        m_surface->ChangeSurfaceBuffer();
        m_surface = NULL;
    }

    g_lock.Unlock();

    ECode ec = NOERROR;

    // if call the Map(), the reference increase; so when the 0 == ref, the
    // Unmap() must be called.
    //
    assert(!m_mapped);

#ifdef _DEBUG
    m_free = TRUE;
#endif

    m_cache->TakeBackBufferNode(this);

    return ec;
}

ECode BufferCacheNode::Map(
    /* [in] */ Int32 x,
    /* [in] */ Int32 y,
    /* [in] */ Int32 width,
    /* [in] */ Int32 height,
    /* [out] */ BitmapBufInfo* info)
{
    assert(info);

    ECode ec = NOERROR;

    Lock();

    Rectangle rect = {x, y, width, height};
    Rectangle clip = {0, 0, m_buffer.info.width, m_buffer.info.height};
    if (!RectangleIntersect(&rect, &clip)) {
        GFX_ERROR_MESSAGE("Not intersect");
        ec = E_RECTANGLE_NOT_INTERSECT;

        goto Exit;
    }

#if defined(_zener)
    if (!m_mapped) {
        assert(m_buffer.info.address == m_mapAddress);
        m_buffer.info.address = (Address)DzRemapDiscontinuousKernelPages(
                        (Void *)m_buffer.info.address,
                        PageAround(m_buffer.size),
                        MemoryProtection_RW);
        if (!m_buffer.info.address) {
            m_buffer.info.address = m_mapAddress;
            GFX_ERROR_MESSAGE("map failed\n");
            ec = E_BUFFER_MAP_FAILED;

            goto Exit;
        }

        assert(0 == m_processId);

        GFX_EXPRESSION_TIP(::GetCurrentProcess()->GetId(&m_processId));
    }

    GFX_EXPRESSION(TRIPLEVEL,
        {ProcessId id;
        ec = ::GetCurrentProcess()->GetId(&id);
        if (GFX_FAILED(ec)) {
            goto Exit;
        }

        assert(id == m_processId);});

#elif defined(_win32)
    assert(m_mapAddress == m_buffer.info.address);
#elif defined(_wince)
    assert(m_mapAddress == m_buffer.info.address);
#else
# add the code of the new platform
#endif // _zener

    info->bitsPerPixel = BITS_PER_PIXEL(m_buffer.info.pixelFormat);
    info->width = rect.width;
    info->height = rect.height;
    info->pitch = m_buffer.info.pitch;
    info->pixelFormat = m_buffer.info.pixelFormat;
    info->address = m_buffer.info.address
                + y * m_buffer.info.pitch
                + x * (m_buffer.info.bitsPerPixel >> 3);

#if defined(_win32)
    ec = ConvertAllocAddrToId(
                    m_buffer.info.address,
                    info->address - m_buffer.info.address,
                    (unsigned long *)&info->address);
    if (GFX_FAILED(ec)) {
        goto Exit;
    }
#endif

    m_mapped = TRUE;
    m_mapCount.Increment();

Exit:
    Unlock();

    return ec;
}

ECode BufferCacheNode::Unmap()
{
    ECode ec = NOERROR;
    Int32 ref;

#ifdef _zener
    ProcessId id;

    ec = ::GetCurrentProcess()->GetId(&id);
    if (GFX_FAILED(ec)) {
        return ec;
    }

    if (id != m_processId) {
        GFX_ERROR_MESSAGE("Unmap buffer, but be not same process with map");

        return E_BUFFER_UNMAP_NOT_SAME_PROCESS;
    }
#endif // _zener

    Lock();

    if (!m_mapped) {
        GFX_ERROR_MESSAGE("The buffer doesn't be mapped\n");
        assert(0 == m_mapCount);
        ec = E_BUFFER_NOT_MAPPED;

        goto Exit;
    }

    ref = m_mapCount.Decrement();
    assert(ref >= 0);
    if (0 == ref) {
#ifdef _zener
        assert(m_mapAddress);
        DzUnmapDiscontinuousKernelPages(
                        (Void *)m_buffer.info.address,
                        PageAround(m_buffer.size));
#endif
        m_mapped = FALSE;

        m_buffer.info.address = m_mapAddress;

        m_processId = 0;
    }

Exit:
    Unlock();

    return ec;
}

ECode BufferCacheNode::Resize(
    /* [in] */ Int32 width,
    /* [in] */ Int32 height)
{
    assert(width > 0 && height > 0);

    Int32 pitch = width * m_buffer.info.bitsPerPixel >> 3;
    Int32 size = AlignPageUp(pitch * height);
    Address addr;
    Address mappedAddr;
    ECode ec = NOERROR;

    assert((m_buffer.info.bitsPerPixel >> 3)
        == BYTES_PER_PIXEL(m_buffer.info.pixelFormat));
    assert(m_buffer.info.bitsPerPixel == BITS_PER_PIXEL(m_buffer.info.pixelFormat));

    // Alloc new buffer
    //
#if defined(_zener)
    addr = (Address)DzAllocDiscontinuousKernelPages(
                    PageAround(size),
                    MemoryProtection_Write);
#elif defined(_win32)
    ec = AllocShareMem(size, MemoryProtection_RW, &addr, NULL);
    if (GFX_FAILED(ec)) {
        addr = 0;
    }
#elif defined(_wince)
    addr = (Address)Aura_malloc(size);
#else
# add the code of the new platform
#endif // _zener

    if (!addr) {
        GFX_ERROR_MESSAGE("Not enough memory\n");
        return E_OUT_OF_MEMORY;
    }

    Lock();

    mappedAddr = addr;

    if (0 != m_mapCount) {
#ifdef _zener
        mappedAddr = (Address)DzRemapDiscontinuousKernelPages(
                        (Void *)addr,
                        PageAround(size),
                        MemoryProtection_RW);
        if (!m_mapAddress) {
            DzFreeDiscontinuousKernelPages((Void *)addr);
            GFX_ERROR_MESSAGE("map failed\n");
            ec = E_BUFFER_MAP_FAILED;

            goto Exit;
        }

        assert(m_mapAddress);
        DzUnmapDiscontinuousKernelPages(
                        (Void *)m_buffer.info.address,
                        PageAround(m_buffer.size));
#endif
    }

#if defined(_zener)
    DzFreeDiscontinuousKernelPages((Void *)m_buffer.info.address);
#elif defined(_win32)
    FreeAllocShareMem(m_buffer.info.address);
#elif defined(_wince)
    Aura_free((Void *)m_buffer.info.address);
#else
# add the code of the new platform
#endif

    m_mapAddress = addr;
    m_buffer.size = size;
    m_buffer.info.address = mappedAddr;
    m_buffer.info.height = height;
    m_buffer.info.width = width;
    m_buffer.info.pitch = pitch;

#ifdef _zener
Exit:
#endif

    Unlock();

    return ec;
}

Void BufferCache::Dump()
{
    BufferCacheNode* node;

    kprintf("\navailable buffer:\n");
    ForEachDLinkNode(BufferCacheNode*, node, &m_availabilityHeader) {
        assert(!node->m_free);
        kprintf("\t");

        if (node->m_surface &&
                node->m_surface->m_attribute & GrafixAttribute_StickyBuffer) {
            kprintf("$");
        }

        kprintf("0x%08x [%d X %d]", node->m_buffer.size,
            node->m_buffer.info.width, node->m_buffer.info.height);

        if (node->m_surface
                && node->m_surface->m_grafixObject) {
            assert(node->m_surface->m_attribute
                == node->m_surface->m_grafixObject->m_config.option);
            kprintf("--%S(0x%08x)", node->m_surface->m_grafixObject->m_title,
                node->m_surface->m_grafixObject);

            if (node->m_surface->m_grafixObject->Transparent()) {
                kprintf("  Transparent");
            }
        }
        kprintf("\n");
    }

    kprintf("\nFree buffer:\n");
    ForEachDLinkNode(BufferCacheNode*, node, &m_invalidHeader) {
        assert(node->m_free);
        kprintf("0x%08x\t", node->m_buffer.size);
    }

    kprintf("\n");

#ifdef _DEBUG
    kprintf("The total buffer = 0x%08x\n", g_windowBufferSize);
#endif
}

Void BufferCache::Destroy()
{
    BufferCacheNode* node = (BufferCacheNode *)m_invalidHeader.Next();

    while (node != &m_invalidHeader) {
        assert(0);
        node->Detach();
        assert(!node->m_mapped);
        assert(0 == node->m_reference);

#if defined(_zener)
        DzFreeDiscontinuousKernelPages((Void *)node->m_buffer.info.address);
#elif defined(_win32)
        FreeAllocShareMem(node->m_buffer.info.address);
#elif defined(_wince)
        Aura_free((void *)node->m_buffer.info.address);
#else
# add the code of the new platform
#endif

        node = (BufferCacheNode *)m_invalidHeader.Next();
    }


    node = (BufferCacheNode *)m_availabilityHeader.Next();
    while (node != &m_availabilityHeader) {
        node->Detach();

        assert(!node->m_mapped);
        assert(0 == node->m_reference);

#if defined(_zener)
        DzFreeDiscontinuousKernelPages((Void *)node->m_buffer.info.address);
#elif defined(_win32)
        FreeAllocShareMem(node->m_buffer.info.address);
#elif defined(_wince)
        Aura_free((void *)node->m_buffer.info.address);
#else
# add the code of the new flatform
#endif
        node = (BufferCacheNode *)m_availabilityHeader.Next();
    }
}

ECode BufferCache::GetBufferNode(
    /* [in] */ CGrafixSurface* surface,
    /* [in] */ Int32 width,
    /* [in] */ Int32 height,
    /* [in] */ GrafixAttribute attribute,
    /* [in] */ PixelFormat format,
    /* [out] */ BufferCacheNode** bufferNode)
{
    assert(surface);
    assert(bufferNode);

    BufferCacheNode* node;
    ECode ec = NOERROR;
    Int32 bpp = BITS_PER_PIXEL(format);
    Int32 size = width * height * bpp >> 3;

    assert((bpp >> 3) == BYTES_PER_PIXEL(format));

    *bufferNode = NULL;

    Lock();

    // Find the buffer in the unusing buffer link.
    //
    if (!m_invalidHeader.IsEmpty()) {
        ForEachDLinkNode(BufferCacheNode*, node, &m_invalidHeader) {
            if (size <= node->m_buffer.size
                    && ((size << 2) > node->m_buffer.size
                        || node->m_buffer.size <= (Int32)PAGE_SIZE)) {
                assert(node->m_free);
                assert(node->m_cache == this);

                assert(NULL == node->m_surface);
                node->m_buffer.info.pixelFormat = format;

                node->m_surface = surface;
                node->m_buffer.info.bitsPerPixel = bpp;
                node->m_buffer.info.width = width;
                node->m_buffer.info.height = height;
                node->m_buffer.attribute = attribute;
                node->m_buffer.info.pitch = width * bpp >> 3;
#ifdef _DEBUG
                node->m_free = FALSE;
#endif

                node->Detach();

                m_availabilityHeader.InsertLast(node);

                node->AddRef();

                *bufferNode = node;
                goto Exit;
            }
        }
    }

    // Create a new node.
    //
    node = new BufferCacheNode(this, surface);
    if (!node) {
        GFX_ERROR_MESSAGE("Not enough memory\n");
        ec = E_OUT_OF_MEMORY;

        goto Exit;
    }

    assert(0 == node->m_processId);

    node->m_buffer.info.bitsPerPixel = bpp;
    node->m_buffer.info.pitch = node->m_buffer.info.bitsPerPixel * width >> 3;
    node->m_buffer.size = AlignPageUp(size);

#if defined(_zener)
    node->m_buffer.info.address = (Address)DzAllocDiscontinuousKernelPages(
                    PageAround(node->m_buffer.size),
                    MemoryProtection_Write);
#elif defined(_win32)
    ec = AllocShareMem(node->m_buffer.size, MemoryProtection_RW,
                    &node->m_buffer.info.address, NULL);
    if (GFX_FAILED(ec)) {
        node->m_buffer.info.address = 0;
    }
#elif defined(_wince)
    node->m_buffer.info.address = (Address)Aura_malloc(node->m_buffer.size);
#else
# add the code of the new flatform
#endif // _zener

    if (!node->m_buffer.info.address) {
        GFX_ERROR_MESSAGE("Not enough memory\n");
        delete node;
        ec = E_OUT_OF_MEMORY;

        goto Exit;
    }

    node->m_surface = surface;
    node->m_buffer.info.pixelFormat = format;
    node->m_buffer.info.width = width;
    node->m_buffer.info.height = height;
    node->m_buffer.attribute = attribute;
    node->m_mapAddress = node->m_buffer.info.address;

    m_availabilityHeader.InsertLast(node);

    node->AddRef();
    *bufferNode = node;

#ifdef _DEBUG
    g_windowBufferSize += node->m_buffer.size;

    GFX_EXPRESSION(DEBUG_BUFFERSIZE, {
        kprintf("Create buffer width = %d, height = %d, size = 0x%08x\n",
            node->m_buffer.info.width, node->m_buffer.info.height,
            node->m_buffer.size);

        if (surface->m_attribute & GrafixAttribute_StickyBuffer) {
            if (surface->m_grafixObject) {
                kprintf("Create buffer for window 0x%08x(%S)\n",
                    surface->m_grafixObject, surface->m_grafixObject->m_title);
            }
            else {
                kprintf("Create surface\n");
            }
        }

        Dump();

        kprintf("\n");
    });
#endif

Exit:
    if (*bufferNode) {
        memset((Void *)node->m_buffer.info.address, 0, size);
    }

    Unlock();

    return ec;
}

ECode BufferCache::TakeBackBufferNode(
    /* [in] */ BufferCacheNode* buffer)
{
    assert(buffer);

    Lock();

    GFX_EXPRESSION(TRIPLEVEL, {
        Boolean alive = FALSE;
        BufferCacheNode* node;

        ForEachDLinkNode(BufferCacheNode*, node, &m_availabilityHeader) {
            if (node == buffer) {
                assert(!alive);
                alive = TRUE;
            }
        }

        assert(alive);
    });

    buffer->Detach();

    assert(buffer->m_free);

#ifdef _zener
    if (buffer->m_mapped) {
        DzUnmapDiscontinuousKernelPages(
                        (Void *)buffer->m_buffer.info.address,
                        PageAround(buffer->m_buffer.size));
        buffer->m_buffer.info.address = buffer->m_mapAddress;
    }
#endif

    if (buffer->m_buffer.size > g_screenSize) {
#ifdef _DEBUG
        assert(g_windowBufferSize >= buffer->m_buffer.size);
        g_windowBufferSize -= buffer->m_buffer.size;

        GFX_EXPRESSION(DEBUG_BUFFERSIZE,
            kprintf("The total buffer = 0x%08x\n", g_windowBufferSize));
#endif

#if defined(_zener)
        DzFreeDiscontinuousKernelPages((Void *)buffer->m_buffer.info.address);
#elif defined(_win32)
        FreeAllocShareMem(buffer->m_buffer.info.address);
#elif defined(_wince)
        Aura_free((void *)buffer->m_buffer.info.address);
#else
# add the code of the new platform
#endif

        delete buffer;
    }
    else if (!buffer->m_buffer.info.address) {
        delete buffer;
    }
    else {
        assert(buffer->m_mapAddress == buffer->m_buffer.info.address);
        buffer->m_mapCount.Exchange(0);
        buffer->m_mapped = FALSE;

        buffer->m_processId = 0;
        buffer->m_reference.Exchange(0);

        Int32 count = 0;
        BufferCacheNode* node;

        ForEachDLinkNode(BufferCacheNode*, node, &m_invalidHeader) {
            assert(node->m_free);

            if (node->m_buffer.size == buffer->m_buffer.size) {
                count++;
            }
        }

        if (count > MAX_SAME_BUFFER_COUNT) {
#ifdef _DEBUG
            assert(g_windowBufferSize >= buffer->m_buffer.size);
            g_windowBufferSize -= buffer->m_buffer.size;

            GFX_EXPRESSION(DEBUG_BUFFERSIZE,
                kprintf("The total buffer = 0x%08x\n", g_windowBufferSize));
#endif

#if defined(_zener)
            DzFreeDiscontinuousKernelPages((Void *)buffer->m_buffer.info.address);
#elif defined(_win32)
            FreeAllocShareMem(buffer->m_buffer.info.address);
#elif defined(_wince)
            Aura_free((void *)buffer->m_buffer.info.address);
#else
# add the code of the new platform
#endif
            delete buffer;
        }
        else {
            m_invalidHeader.InsertFirst(buffer);
        }
    }

    Unlock();

    return NOERROR;
}

